popiart-server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

157 lines
4.9 KiB

import Link from "next/link";
import {
PopiartApiError,
getSkillsCatalog,
getViewerSession,
} from "@/lib/popiart-api";
import { getLiveCopy } from "@/lib/live-copy";
import { type Locale } from "@/lib/site-content";
export const dynamic = "force-dynamic";
function formatError(error: unknown) {
if (error instanceof PopiartApiError) {
return error.message;
}
if (error instanceof Error) {
return error.message;
}
return String(error);
}
export default async function SkillsPage({
params,
searchParams,
}: {
params: Promise<{ locale: string }>;
searchParams: Promise<{ search?: string | string[] }>;
}) {
const { locale } = await params;
const rawSearch = (await searchParams).search;
const search = Array.isArray(rawSearch) ? rawSearch[0] : rawSearch;
const typedLocale = locale as Locale;
const liveCopy = getLiveCopy(typedLocale);
const session = await getViewerSession();
if (!session) {
return (
<div className="page-stack">
<section className="section-panel hero-tight">
<div className="section-heading">
<div className="eyebrow">{liveCopy.skills.tag}</div>
<h1>{liveCopy.skills.title}</h1>
<p>{liveCopy.skills.subtitle}</p>
</div>
</section>
<section className="section-panel empty-state-card">
<div className="section-heading compact">
<h2>{liveCopy.skills.unauthenticatedTitle}</h2>
<p>{liveCopy.skills.unauthenticatedBody}</p>
</div>
<div className="hero-actions">
<Link className="button button-dark" href={`/${locale}/login`}>
{liveCopy.skills.loginCta}
</Link>
<Link className="button button-light" href={`/${locale}/docs`}>
{liveCopy.skills.docsCta}
</Link>
</div>
</section>
</div>
);
}
let catalog = null;
let loadError: string | null = null;
try {
catalog = await getSkillsCatalog(search?.trim());
} catch (error) {
loadError = formatError(error);
}
return (
<div className="page-stack">
<section className="section-panel hero-tight">
<div className="section-heading">
<div className="eyebrow">{liveCopy.skills.tag}</div>
<h1>{liveCopy.skills.title}</h1>
<p>{liveCopy.skills.subtitle}</p>
</div>
</section>
<section className="section-panel">
<form className="search-form" method="get">
<label className="field-label" htmlFor="skill-search">
{liveCopy.skills.searchLabel}
</label>
<div className="search-row">
<input
className="text-input"
defaultValue={search ?? ""}
id="skill-search"
name="search"
placeholder={liveCopy.skills.searchPlaceholder}
type="search"
/>
<button className="button button-dark" type="submit">
{liveCopy.skills.searchAction}
</button>
</div>
</form>
{loadError ? (
<div className="status-banner status-banner-error">
<strong>{liveCopy.skills.loadErrorPrefix}</strong>
<span>{loadError}</span>
</div>
) : null}
<div className="pill-row">
<span className="pill">
{liveCopy.skills.resultsPrefix}: {catalog?.total ?? 0}
</span>
<span className="pill">{session.user.name || session.user.email || session.user.id}</span>
</div>
</section>
<section className="catalog-grid">
{catalog && catalog.items.length > 0 ? (
catalog.items.map((skill) => (
<article className="feature-card skill-card" key={skill.id}>
<span className="card-kicker">{skill.version}</span>
<h3>{skill.name}</h3>
<p>{skill.description}</p>
<dl className="meta-grid">
<div>
<dt>{liveCopy.skills.routeKey}</dt>
<dd>{skill.route_key || skill.id}</dd>
</div>
<div>
<dt>{liveCopy.skills.modelType}</dt>
<dd>{skill.model_type}</dd>
</div>
<div>
<dt>{liveCopy.skills.latency}</dt>
<dd>{skill.estimated_duration_s}s</dd>
</div>
<div>
<dt>{liveCopy.skills.tags}</dt>
<dd>{skill.tags.join(", ") || "-"}</dd>
</div>
</dl>
</article>
))
) : (
<article className="section-panel empty-state-card">
<div className="section-heading compact">
<h2>{liveCopy.skills.noResults}</h2>
<p>{search ? `"${search}"` : liveCopy.skills.subtitle}</p>
</div>
</article>
)}
</section>
</div>
);
}